#include "../lib/error.h"
#include <arpa/inet.h>      /* struct linger */
#include <netinet/in.h>
#include <netinet/tcp.h>    /* for TCP_xxx defines */
#include <netinet/tcp.h>    /* for TCP_xxx defines */
#include <stdio.h>
#include <stdlib.h>         /* exit */
#include <sys/time.h>       /* struct timeval */
#include <unistd.h>         /* close */

union val {
    int             i_val;
    long            l_val;
    struct linger   linger_val;
    struct timeval  timeval_val;
} val;

// struct linger {
// 　　int l_onoff;
// 　　int l_linger;
// };
//
// struct timeval
// {
//     time_t tv_sec; /* Seconds. */
//     suseconds_t tv_usec; /* Microseconds. */
// };

static char *sock_str_flag(union val *, int);
static char *sock_str_int(union val *, int);
static char *sock_str_linger(union val *, int);
static char *sock_str_timeval(union val *, int);

struct sock_opts {
    const char *opt_str;
    int opt_level;
    int opt_name;
    char *(*opt_val_str)(union val *, int);
} sock_opts[] = {
    { "SO_BROADCAST",       SOL_SOCKET,     SO_BROADCAST,       sock_str_flag },
    { "SO_DEBUG",           SOL_SOCKET,     SO_DEBUG,           sock_str_flag },
    { "SO_DONTROUTE",       SOL_SOCKET,     SO_DONTROUTE,       sock_str_flag },
    { "SO_ERROR",           SOL_SOCKET,     SO_ERROR,           sock_str_int  },
    { "SO_KEEPALIVE",       SOL_SOCKET,     SO_KEEPALIVE,       sock_str_flag },
    { "SO_LINGER",          SOL_SOCKET,     SO_LINGER,          sock_str_linger },
    { "SO_OOBINLINE",       SOL_SOCKET,     SO_OOBINLINE,       sock_str_flag},
    { "SO_RCVBUF",          SOL_SOCKET,     SO_RCVBUF,          sock_str_int },
    { "SO_SNDBUF",          SOL_SOCKET,     SO_SNDBUF,          sock_str_int },
    { "SO_RCVLOWAT",        SOL_SOCKET,     SO_RCVLOWAT,        sock_str_int },
    { "SO_SNDLOWAT",        SOL_SOCKET,     SO_SNDLOWAT,        sock_str_int },
    { "SO_RCVTIMEO",        SOL_SOCKET,     SO_RCVTIMEO,        sock_str_timeval },
    { "SO_SNDTIMEO",        SOL_SOCKET,     SO_SNDTIMEO,        sock_str_timeval },
    { "SO_REUSEADDR",       SOL_SOCKET,     SO_REUSEADDR,       sock_str_flag },
#ifdef SO_REUSEPORT
    { "SO_REUSEPORT",       SOL_SOCKET,     SO_REUSEPORT,       sock_str_flag },
#else
    { "SO_REUSEPORT",       0,              0,                  NULL },
#endif
    { "SO_TYPE",            SOL_SOCKET,     SO_TYPE,            sock_str_int },
//    { "SO_USELOOPBACK",     SOL_SOCKET,     SO_USELOOPBACK,     sock_str_flag },
    { "IP_TOS",             IPPROTO_IP,     IP_TOS,             sock_str_int },
    { "IP_TTL",             IPPROTO_IP,     IP_TTL,             sock_str_int },
    { "IPV6_DONTFRAG",      IPPROTO_IPV6,   IPV6_DONTFRAG,      sock_str_flag },
    { "IPV6_UNICAST_HOPS",  IPPROTO_IPV6,   IPV6_UNICAST_HOPS,  sock_str_int},
    { "IPV6_V6ONLY",        IPPROTO_IPV6,   IPV6_V6ONLY,        sock_str_flag },
    { "TCP_MAXSEG",         IPPROTO_TCP,    TCP_MAXSEG,         sock_str_int },
    { "TCP_NODELAY",        IPPROTO_TCP,    TCP_NODELAY,        sock_str_flag },
//     { "SCTP_AUTOCLOSE",     IPPROTO_SCTP,   SCTP_AUTOCLOSE,     sock_str_int },
//     { "SCTP_MAXBURST",      IPPROTO_SCTP,   SCTP_MAXBURST,      sock_str_int },
//     { "SCTP_MAXSEG",        IPPROTO_SCTP,   SCTP_MAXSEG,        sock_str_int },
//     { "SCTP_NODELAY",       IPPROTO_SCTP,   SCTP_NODELAY,       sock_str_flag },
    { NULL,                 0,              0,                  NULL }
};

// gcc checkopts.c error.c error.h
int main(int argc, char **argv) {
    int                 fd;
    socklen_t           len;
    struct sock_opts   *ptr;

    for (ptr = sock_opts; ptr->opt_str != NULL; ptr++) {
        printf("%s: ", ptr->opt_str);
        if (ptr->opt_val_str == NULL) {
            printf("(undefined)\n");
        } else {
            switch (ptr->opt_level) {
                case SOL_SOCKET:
                case IPPROTO_IP:
                case IPPROTO_TCP:
                    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
                        err_sys("socket error");
                    }
                    break;
#ifdef IPV6
                case IPPROTO_IPV6:
                    if ((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {
                        err_sys("socket error")
                    }
                    break;
#endif
                default:
                    err_quit("Can't create fd for level %d\n", ptr->opt_level);
            }

            len = sizeof(val);
            if (getsockopt(fd, ptr->opt_level, ptr->opt_name, &val, &len) == -1) {
                err_ret("getsocket error");
            } else {
                printf("default = %s\n", (*ptr->opt_val_str)(&val, len));
            }
            close(fd);
        }
    }
    exit(0);
}

static char strres[128];

static char *sock_str_flag(union val *ptr, int len) {
    if (len != sizeof(int)) {
        snprintf(strres, sizeof(strres), "size (%d) not sizeof(int)", len);
    } else {
        snprintf(strres, sizeof(strres), "%s", (ptr->i_val == 0) ? "off" : "on");
    }
    return strres;
}

static char *sock_str_int(union val *ptr, int len) {
    if (len != sizeof(int)) {
        snprintf(strres, sizeof(strres), "size (%d) not sizeof(int)", len);
    } else {
        snprintf(strres, sizeof(strres), "%s", (ptr->l_val == 0) ? "off" : "on");
    }
    return strres;
}

static char *sock_str_linger(union val *ptr, int len) {
    if (len != sizeof(struct linger)) {
        snprintf(strres, sizeof(strres), "size (%d) not sizeof(int)", len);
    } else {
        snprintf(strres, sizeof(strres), "l_onoff = %d, l_linger = %d", ptr->linger_val.l_onoff, ptr->linger_val.l_linger);
    }
    return strres;
}

static char *sock_str_timeval(union val *ptr, int len) {
    if (len != sizeof(struct timeval)) {
        snprintf(strres, sizeof(strres), "size (%d) not sizeof(int)", len);
    } else {
        snprintf(strres, sizeof(strres), "%ld sec, %ld usec", ptr->timeval_val.tv_sec,  ptr->timeval_val.tv_usec);
    }
    return strres;
}

/*
    $ ./checkopts
    SO_BROADCAST: default = off
    SO_DEBUG: default = off
    SO_DONTROUTE: default = off
    SO_ERROR: default = off
    SO_KEEPALIVE: default = off
    SO_LINGER: default = l_onoff = 0, l_linger = 0
    SO_OOBINLINE: default = off
    SO_RCVBUF: default = on
    SO_SNDBUF: default = on
    SO_RCVLOWAT: default = on
    SO_SNDLOWAT: default = on
    SO_RCVTIMEO: default = 0 sec, 0 usec
    SO_SNDTIMEO: default = 0 sec, 0 usec
    SO_REUSEADDR: default = off
    SO_REUSEPORT: default = off
    SO_TYPE: default = on
    IP_TOS: default = off
    IP_TTL: default = on
    IPV6_DONTFRAG: Can't create fd for level 41
*/
